/******************************************************************************
 * * nav.c - implementation of UI navigation functions
 *
 * *(C) Copyright 2019 Asr International Ltd.
 * * All Rights Reserved
 * ******************************************************************************/

/*********************
*      INCLUDES
*********************/
#include <stdio.h>
#include <string.h>
#include "nav.h"
#include "inter_common_ui_interface.h"

/*********************
*      DEFINES
*********************/
#define PRINT_STACK    1 /* Print stack info - for debug */

/**********************
*  STATIC VARIABLES
**********************/
static Nav_Stack_t Stack[ACTIVITY_STACK_SIZE]; /* Activity stack */
static INT8        TopIndex = -1;              /* The index of top activity in stack */

/**********************
*  STATIC PROTOTYPES
**********************/
static void Push(ACTIVITY_ID ActId, Nav_Func_List_t *FuncList, void *Param);
static ACTIVITY_ID Pop(void);
static void Pop_To(INT8 Index);
static Nav_Stack_t *Top(void);
static BOOL Is_Empty(void);
static BOOL Is_Full(void);
static INT8 Get_Element_Index(ACTIVITY_ID ActId);
static INT8 Get_Element_Count(void);
static void Move_Element(INT8 IndexFrom, INT8 Count, INT8 IndexTo);

#if PRINT_STACK
static void Print_Stack(void);

#endif

/**********************
*   GLOBAL FUNCTIONS
**********************/

/**
 * Navigate init.
 * should be called before calling any other Nav functions.
 */
void Nav_Init(void)
{
    memset(Stack, 0, sizeof(Nav_Stack_t) * ACTIVITY_STACK_SIZE);
    for (UINT8 i = 0; i < ACTIVITY_STACK_SIZE; i++)
    {
        Stack[i].ActId = ACT_ID_MAX;
    }

    TopIndex = -1;
}

/**
 * Navigate reset.
 */
void Nav_Reset(void)
{
    if (0 <= TopIndex)
    {
        UINT8 i = TopIndex;
        Stack[i].FuncList.OnHide();

        for(i = 0;i <= TopIndex;i++)
        {
            if (Stack[i].Param)
            {
                Hal_Mem_Free(Stack[i].Param);
            }
            memset(&Stack[i], 0, sizeof(Nav_Stack_t));
            Stack[i].ActId = ACT_ID_MAX;
        }

        TopIndex = -1;
    }
}

/**
 * Navigate forward to the specified activity.
 * This method will find the first activity which ID is equal to 'ActId' in the stack from top to bottom,
 * if the activity is not existed, then create a new activity and push it into the stack;
 * if the activity is existed, then pop to that activity.
 * param (in) SrcActId:  source activity ID
 * param (in) DestActId: destination activity ID
 * param (in) FuncList:  function list
 * param (in) Param:  parameter passed to the forward activity
 * return void
 */
void Nav_Forward(ACTIVITY_ID SrcActId, ACTIVITY_ID DestActId, Nav_Func_List_t *FuncList, void *Param)
{
    INT8 IndexOfDest = -1;
    INT8 IndexOfSrc  = -1;
    INT8 IndexOfTop  = TopIndex;

    printf("Nav_Forward: SrcActId=%d, DestActId=%d\n", SrcActId, DestActId);

    if ((!Is_Empty()) && (Top()->ActId == DestActId))    /* Keep single top */
    {
        printf("DestActId (%d) is already on the top\n", DestActId);
        return;
    }

    if ((SrcActId == ACT_ID_ANY) || (SrcActId == Top()->ActId))  /* normal case */
    {
        IndexOfDest = Get_Element_Index(DestActId);

        if (IndexOfDest < 0)   /* The 'DestActId' activity is not existed in stack */
        {
            if (!Is_Empty())
            {
                /* Save state of current activity */
                if (Top()->FuncList.OnSaveState)
                {
                    Top()->FuncList.OnSaveState(Top()->Param);
                }

                /* Hide current activity */
                Top()->FuncList.OnHide();
            }

            /* Push 'DestActId' activity into stack */
            Push(DestActId, FuncList, Param);

            /* Show 'DestActId' activity */
            Top()->FuncList.OnShow(Param);

            /* Create 'DestActId' activity */
            if (Top()->FuncList.OnCreate)
            {
                Top()->FuncList.OnCreate(Top()->Param);
            }
        }
        else
        {
            /* Destroy current activity */
            if (Top()->FuncList.OnDestroy)
            {
                Top()->FuncList.OnDestroy(Top()->Param);
            }

            /* Hide current activity */
            Top()->FuncList.OnHide();

            /* Pop to 'DestActId' activity */
            Pop_To(IndexOfDest);

            if ((NULL != Param) && (Top()->Param != Param))
            {
                printf("memory leak in Nav_Forward: DestActId=%d\n", DestActId);
            }

            /* Show 'DestActId' activity */
            Top()->FuncList.OnShow(Top()->Param);

            /* Restore state of 'DestActId' activity */
            if (Top()->FuncList.OnRestoreState)
            {
                Top()->FuncList.OnRestoreState(Top()->Param);
            }
        }
    }
    else    /* special case for auto quit */
    {
        IndexOfSrc = Get_Element_Index(SrcActId);
        if (IndexOfSrc < 0)
        {
            printf("SrcActId (%d) is not in stack in Nav_Forward\n", SrcActId);
            return;
        }

        IndexOfDest = Get_Element_Index(DestActId);
        if (IndexOfDest < 0)   /* The 'DestActId' activity is not existed in stack */
        {
            printf("DestActId (%d) is not in stack in Nav_Forward. This operation is not supported for special case.\n", DestActId);
            return;
        }

        TopIndex = IndexOfSrc;

        /* Destroy source activity */
        if (Top()->FuncList.OnDestroy)
        {
            Top()->FuncList.OnDestroy(Top()->Param);
        }

        /* Pop to 'DestActId' activity */
        Pop_To(IndexOfDest);

        if ((NULL != Param) && (Top()->Param != Param))
        {
            printf("memory leak in Nav_Forward: DestActId=%d\n", DestActId);
        }

        /* Move element */
        Move_Element(IndexOfSrc + 1, IndexOfTop - IndexOfSrc, TopIndex + 1);
    }

#if PRINT_STACK
    Print_Stack();
#endif
} /* Nav_Forward */

/**
 * Navigate backward to the previous activity.
 * param (in) SrcActId:  source activity ID
 */
void Nav_Back(ACTIVITY_ID SrcActId)
{
    INT8 IndexOfSrc = -1;
    INT8 IndexOfTop = TopIndex;

    printf("Nav_Back: SrcActId=%d\n", SrcActId);

    if (Is_Empty())
    {
        return;
    }

    if ((SrcActId == ACT_ID_ANY) || (SrcActId == Top()->ActId))    /* normal case */
    {
        /* Destroy current activity */
        if (Top()->FuncList.OnDestroy)
        {
            Top()->FuncList.OnDestroy(Top()->Param);
        }

        /* Hide current activity */
        Top()->FuncList.OnHide();

        /* Pop current activity from stack */
        Pop();

        if (!Is_Empty())
        {
            /* Show previous activity */
            Top()->FuncList.OnShow(Top()->Param);

            /* Restore state of previous activity */
            if (Top()->FuncList.OnRestoreState)
            {
                Top()->FuncList.OnRestoreState(Top()->Param);
            }
        }
    }
    else    /* special case for auto quit */
    {
        IndexOfSrc = Get_Element_Index(SrcActId);
        if (IndexOfSrc < 0)
        {
            printf("SrcActId (%d) is not in stack in Nav_Back\n", SrcActId);
            return;
        }

        TopIndex = IndexOfSrc;

        /* Destroy source activity */
        if (Top()->FuncList.OnDestroy)
        {
            Top()->FuncList.OnDestroy(Top()->Param);
        }

        /* Pop source activity from stack */
        Pop();

        /* Move element */
        Move_Element(IndexOfSrc + 1, IndexOfTop - IndexOfSrc, TopIndex + 1);
    }

#if PRINT_STACK
    Print_Stack();
#endif
} /* Nav_Back */

/**
 * Navigate to home, i.e. desktop activity.
 * param (in) SrcActId:  source activity ID
 */
void Nav_Home(ACTIVITY_ID SrcActId)
{
    INT8 IndexOfSrc = -1;
    INT8 IndexOfTop = TopIndex;

    if (Get_Element_Count() <= 1) /* Only desktop activity left */
    {
        return;
    }

    if ((SrcActId == ACT_ID_ANY) || (SrcActId == Top()->ActId))    /* normal case */
    {
        /* Destroy current activity */
        if (Top()->FuncList.OnDestroy)
        {
            Top()->FuncList.OnDestroy(Top()->Param);
        }

        /* Hide current activity */
        Top()->FuncList.OnHide();

        /* Pop all activities from stack except desktop */
        INT8 Index = Get_Element_Index(ACT_ID_DESKTOP);
        Pop_To(Index);

        /* Show desktop activity*/
        Top()->FuncList.OnShow(Top()->Param);

        /* Restore state of desktop activity */
        if (Top()->FuncList.OnRestoreState)
        {
            Top()->FuncList.OnRestoreState(Top()->Param);
        }
    }
    else    /* special case for auto quit */
    {
        IndexOfSrc = Get_Element_Index(SrcActId);
        if (IndexOfSrc < 0)
        {
            printf("SrcActId (%d) is not in stack in Nav_Home\n", SrcActId);
            return;
        }

        TopIndex = IndexOfSrc;

        /* Destroy source activity */
        if (Top()->FuncList.OnDestroy)
        {
            Top()->FuncList.OnDestroy(Top()->Param);
        }

        /* Pop all activities from stack except desktop */
        INT8 Index = Get_Element_Index(ACT_ID_DESKTOP);
        Pop_To(Index);

        /* Move element */
        Move_Element(IndexOfSrc + 1, IndexOfTop - IndexOfSrc, TopIndex + 1);
    }

#if PRINT_STACK
    Print_Stack();
#endif
} /* Nav_Home */

/**
 * Navigate backward to 'BackwardId' activity in the stack,
 * and then forward to 'ForwardId' activity
 * param (in) SrcActId:  source activity ID
 * param (in) BackwardId:  activity ID backward to
 * param (in) ForwardId:   activity ID forward to
 * param (in) ForwardFuncList:  function list of 'ForwardId' activity
 * param (in) ForwardParam:    parameter of 'ForwardId' activity
 * return void
 */
void Nav_Backward_And_Forward(ACTIVITY_ID SrcActId, ACTIVITY_ID BackwardId, ACTIVITY_ID ForwardId, Nav_Func_List_t *ForwardFuncList, void *ForwardParam)
{
    INT8 IndexBackward = -1;
    INT8 IndexForward  = -1;
    INT8 IndexOfSrc    = -1;
    INT8 IndexOfTop    = TopIndex;

    IndexBackward = Get_Element_Index(BackwardId);
    if (IndexBackward < 0)
    {
        printf("BackwardId (%d) is not in the stack\n", BackwardId);
        return;
    }

    if ((!Is_Empty()) && (Top()->ActId == BackwardId))
    {
        printf("BackwardId (%d) is on the top\n", BackwardId);
        return;
    }

    IndexForward = Get_Element_Index(ForwardId);
    if ((IndexForward >= 0) && (IndexForward <= IndexBackward))
    {
        printf("ForwardId (%d) is in the stack, and IndexForward(%d) <= IndexBackward(%d)\n", ForwardId, IndexForward, IndexBackward);
        return;
    }

    if ((SrcActId == ACT_ID_ANY) || (SrcActId == Top()->ActId))    /* normal case */
    {
        /* Destroy current activity */
        if (Top()->FuncList.OnDestroy)
        {
            Top()->FuncList.OnDestroy(Top()->Param);
        }

        /* Hide current activity */
        Top()->FuncList.OnHide();

        /* Pop to 'BackwardId' activity */
        Pop_To(IndexBackward);

        /* Push 'ForwardId' activity into stack */
        Push(ForwardId, ForwardFuncList, ForwardParam);

        /* Show 'ForwardId' activity */
        Top()->FuncList.OnShow(ForwardParam);

        /* Create 'ForwardId' activity */
        if (Top()->FuncList.OnCreate)
        {
            Top()->FuncList.OnCreate(Top()->Param);
        }
    }
    else    /* special case for auto quit */
    {
        IndexOfSrc = Get_Element_Index(SrcActId);
        if (IndexOfSrc < 0)
        {
            printf("SrcActId (%d) is not in stack in Nav_Backward_And_Forward\n", SrcActId);
            return;
        }

        TopIndex = IndexOfSrc;

        /* Destroy source activity */
        if (Top()->FuncList.OnDestroy)
        {
            Top()->FuncList.OnDestroy(Top()->Param);
        }

        /* Pop to 'BackwardId' activity */
        Pop_To(IndexBackward);

        /* Push 'ForwardId' activity into stack */
        Push(ForwardId, ForwardFuncList, ForwardParam);

        /* Move element */
        Move_Element(IndexOfSrc + 1, IndexOfTop - IndexOfSrc, TopIndex + 1);
    }

#if PRINT_STACK
    Print_Stack();
#endif
} /* Nav_Backward_And_Forward */

/**
 * Get top activity ID.
 * param void
 * return ACTIVITY_ID
 */
ACTIVITY_ID Nav_Get_Top(void)
{
    ACTIVITY_ID ActId = ACT_ID_MAX;

    if (!Is_Empty())
    {
        ActId = Top()->ActId;
    }

    return(ActId);
}

/**
 * Get previous activity ID.
 * param ActId:  activity ID
 * return previous activity ID
 */
ACTIVITY_ID Nav_Get_Prev(ACTIVITY_ID ActId)
{
    ACTIVITY_ID PrevActId = ACT_ID_MAX;
    INT8 Index = Get_Element_Index(ActId);

    if (Index > 0)
    {
        PrevActId = Stack[Index - 1].ActId;
    }

    printf("Nav_Get_Prev: ActId=%d, PrevActId=%d\n", ActId, PrevActId);
    return PrevActId;
}

/**
 * Get Param from stack.
 * param ActId:  ActId you want to get param
 * return void
 */
void* Nav_Get_Param(ACTIVITY_ID ActId)
{
    void* Param = NULL;

    for (UINT8 i = 0; i <= TopIndex; i++)
    {
        if (ActId == Stack[i].ActId)
        {
            Param = Stack[i].Param;
            break;
        }
    }

    return Param;
}

/**
 * Get index by ActId.
 * param ActId:  activity ID
 * return index, -1 if not found
 */
INT8 Nav_Get_Index(ACTIVITY_ID ActId)
{
    INT8 Index = Get_Element_Index(ActId);
    printf("Nav_Get_Index: ActId=%d, Index=%d\n", ActId, Index);

    return Index;
}

/**********************
*   STATIC FUNCTIONS
**********************/

/**
 * Push activity into stack.
 * param (in) ActId:  activity ID
 * return void
 */
static void Push(ACTIVITY_ID ActId, Nav_Func_List_t *FuncList, void *Param)
{
    if (Is_Full())
    {
        printf("stack is full in push()\n");
        return;
    }

    UINT8 Index = ++TopIndex;
    Stack[Index].ActId = ActId;
    Stack[Index].Param = Param;

    if (FuncList)
    {
        memcpy(&Stack[Index].FuncList, FuncList, sizeof(Nav_Func_List_t));

        if (NULL == FuncList->OnHide)
        {
            Stack[Index].FuncList.OnHide = UI_Normal_Destroy;
        }
    }
}

/**
 * Pop activity from stack.
 * param:  void
 * return the popped activity ID
 */
static ACTIVITY_ID Pop(void)
{
    if (Is_Empty())
    {
        printf("stack is empty in Pop()\n");
        return(ACT_ID_MAX);
    }

    UINT8 Index = TopIndex--;
    return(Stack[Index].ActId);
}

/**
 * Pop to the specified index position in stack.
 * param Index:  index you want to pop to
 * return void
 */
static void Pop_To(INT8 Index)
{
    Void_P_Func_t OnDestry = NULL;

    if ((Index < 0) || (Index >= TopIndex))
    {
        printf("index (%d) is out of range in Pop_To()\n", Index);
        return;
    }

    /* destroy the activities from TopIndex-1 to index+1 */
    for (UINT8 i = TopIndex - 1; i > Index; i--)
    {
        OnDestry = Stack[i].FuncList.OnDestroy;
        if (OnDestry)
        {
            OnDestry(Stack[i].Param);
        }
    }

    TopIndex = Index;
}

/**
 * Get the top activity in stack.
 * param:  void
 * return pointer to the top activity
 */
static Nav_Stack_t *Top(void)
{
    if (Is_Empty())
    {
        printf("stack is empty in Top()\n");
        return(NULL);
    }

    UINT8 Index = TopIndex;
    return(&Stack[Index]);
}

/**
 * Judge whether stack is empty.
 * param:  void
 * return true if stack is empty, false otherwise
 */
static BOOL Is_Empty(void)
{
    return(TopIndex == -1);
}

/**
 * Judge whether stack is full.
 * param:  void
 * return true if stack is full, false otherwise
 */
static BOOL Is_Full(void)
{
    return(TopIndex == ACTIVITY_STACK_SIZE - 1);
}

/**
 * Get element index by ActId.
 * param ActId:  activity ID
 * return element index
 */
static INT8 Get_Element_Index(ACTIVITY_ID ActId)
{
    INT8 Index = -1;

    for (UINT8 i = 0; i <= TopIndex; i++)
    {
        if (ActId == Stack[i].ActId)
        {
            Index = i;
            break;
        }
    }

    return(Index);
}

/**
 * Get element count.
 * param:  void
 * return count of element
 */
static INT8 Get_Element_Count(void)
{
    return(TopIndex + 1);
}

/**
 * Move elements in stack.
 * IndexFrom should >= IndexTo
 *
 * param IndexFrom:  index from which to move
 * param Count:  count of elements to move
 * param IndexTo:  index to which to move
 * return void
 */
static void Move_Element(INT8 IndexFrom, INT8 Count, INT8 IndexTo)
{
    UINT8 Index = IndexTo;

    if (  (IndexFrom < 0)
       || (IndexTo < 0)
       || (Count <= 0)
       || (IndexFrom < IndexTo))
    {
        printf("IndexFrom=%d, Count=%d, IndexTo=%d, TopIndex=%d in Move_Element", IndexFrom, Count, IndexTo, TopIndex);
        return;
    }

    if (IndexFrom > IndexTo)
    {
        for (UINT8 i = IndexFrom; i < IndexFrom + Count; i++)
        {
            memcpy(&Stack[Index++], &Stack[i], sizeof(Nav_Stack_t));
        }
    }

    TopIndex += Count;
}

#if PRINT_STACK

/**
 * Print stack info - for debug.
 */
static void Print_Stack(void)
{
    printf("stack count = %d, element = ", TopIndex + 1);

    for (UINT8 i = 0; i <= TopIndex; i++)
    {
        printf("%d, ", Stack[i].ActId);
    }
    printf("\n");
}
#endif
